a tool for shared writing and social publishing
1import { z } from "zod"; 2import { 3 PullRequest, 4 PullResponseV1, 5 VersionNotSupportedResponse, 6} from "replicache"; 7import type { Fact } from "src/replicache"; 8import { FactWithIndexes } from "src/replicache/utils"; 9import type { Attribute } from "src/replicache/attributes"; 10import { makeRoute } from "../lib"; 11import type { Env } from "./route"; 12 13// First define the sub-types for V0 and V1 requests 14const pullRequestV0 = z.object({ 15 pullVersion: z.literal(0), 16 schemaVersion: z.string(), 17 profileID: z.string(), 18 cookie: z.any(), // ReadonlyJSONValue 19 clientID: z.string(), 20 lastMutationID: z.number(), 21}); 22 23// For the Cookie type used in V1 24const cookieType = z.union([ 25 z.null(), 26 z.string(), 27 z.number(), 28 z 29 .object({ 30 order: z.union([z.string(), z.number()]), 31 }) 32 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property 33]); 34 35const pullRequestV1 = z.object({ 36 pullVersion: z.literal(1), 37 schemaVersion: z.string(), 38 profileID: z.string(), 39 cookie: cookieType, 40 clientGroupID: z.string(), 41}); 42 43// Combined PullRequest type 44const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]); 45 46export const pull = makeRoute({ 47 route: "pull", 48 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }), 49 handler: async ({ pullRequest, token_id }, { supabase }: Env) => { 50 let body = pullRequest; 51 if (body.pullVersion === 0) return versionNotSupported; 52 let { data, error } = await supabase.rpc("pull_data", { 53 token_id, 54 client_group_id: body.clientGroupID, 55 }); 56 if (!data) { 57 console.log(error); 58 59 return { 60 error: "ClientStateNotFound", 61 } as const; 62 } 63 64 let facts = data.facts as { 65 attribute: string; 66 created_at: string; 67 data: any; 68 entity: string; 69 id: string; 70 updated_at: string | null; 71 version: number; 72 }[]; 73 let publication_data = data.publications as { 74 description: string; 75 title: string; 76 tags: string[]; 77 cover_image: string | null; 78 }[]; 79 let pub_patch = publication_data?.[0] 80 ? [ 81 { 82 op: "put", 83 key: "publication_description", 84 value: publication_data[0].description, 85 }, 86 { 87 op: "put", 88 key: "publication_title", 89 value: publication_data[0].title, 90 }, 91 { 92 op: "put", 93 key: "publication_tags", 94 value: publication_data[0].tags || [], 95 }, 96 { 97 op: "put", 98 key: "publication_cover_image", 99 value: publication_data[0].cover_image || null, 100 }, 101 ] 102 : []; 103 104 let clientGroup = ( 105 (data.client_groups as { 106 client_id: string; 107 client_group: string; 108 last_mutation: number; 109 }[]) || [] 110 ).reduce( 111 (acc, clientRecord) => { 112 acc[clientRecord.client_id] = clientRecord.last_mutation; 113 return acc; 114 }, 115 {} as { [clientID: string]: number }, 116 ); 117 118 return { 119 cookie: Date.now(), 120 lastMutationIDChanges: clientGroup, 121 patch: [ 122 { op: "clear" }, 123 { op: "put", key: "initialized", value: true }, 124 ...(facts || []).map((f) => { 125 return { 126 op: "put", 127 key: f.id, 128 value: FactWithIndexes(f as unknown as Fact<Attribute>), 129 } as const; 130 }), 131 ...pub_patch, 132 ], 133 } as PullResponseV1; 134 }, 135}); 136 137const versionNotSupported: VersionNotSupportedResponse = { 138 error: "VersionNotSupported", 139 versionType: "pull", 140};